package ece1779.ec2;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Vector;

import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.cloudwatch.AmazonCloudWatch;
import com.amazonaws.services.cloudwatch.AmazonCloudWatchClient;
import com.amazonaws.services.cloudwatch.model.Dimension;
import com.amazonaws.services.cloudwatch.model.GetMetricStatisticsRequest;
import com.amazonaws.services.cloudwatch.model.GetMetricStatisticsResult;
import com.amazonaws.services.cloudwatch.model.ListMetricsRequest;
import com.amazonaws.services.cloudwatch.model.ListMetricsResult;
import com.amazonaws.services.cloudwatch.model.Metric;
import com.amazonaws.services.ec2.AmazonEC2;
import com.amazonaws.services.ec2.AmazonEC2Client;
import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.ec2.model.Reservation;
import com.amazonaws.services.ec2.model.RunInstancesRequest;
import com.amazonaws.services.ec2.model.RunInstancesResult;
import com.amazonaws.services.ec2.model.TerminateInstancesRequest;
import com.amazonaws.services.ec2.model.TerminateInstancesResult;
import com.amazonaws.services.elasticloadbalancing.AmazonElasticLoadBalancing;
import com.amazonaws.services.elasticloadbalancing.AmazonElasticLoadBalancingClient;
import com.amazonaws.services.elasticloadbalancing.model.DeregisterInstancesFromLoadBalancerRequest;
import com.amazonaws.services.elasticloadbalancing.model.DeregisterInstancesFromLoadBalancerResult;
import com.amazonaws.services.elasticloadbalancing.model.RegisterInstancesWithLoadBalancerRequest;
import com.amazonaws.services.elasticloadbalancing.model.RegisterInstancesWithLoadBalancerResult;


public class InstanceManager {

    private static final String imageId = "ami-34ff2d5d";
    private static final String keyName = "Sheng";
    private static final String lbName = "Group1";
    
    private static RunInstancesRequest getRunSettings(){
        RunInstancesRequest request = new RunInstancesRequest(imageId,1,1);
    	request.setKeyName(keyName);
    	request.withMonitoring(true);
        return request;
    }
    
    public static void startInstances(BasicAWSCredentials awsCredentials, int expandRatio, PrintWriter out) {
    
	    List<String> instanceNames = getInstanceNames(awsCredentials, out);
	    AmazonEC2 ec2 = new AmazonEC2Client(awsCredentials);
	    AmazonElasticLoadBalancing elasticLoadBalancer = new AmazonElasticLoadBalancingClient(awsCredentials);
	
	    try {
			int currentNumberOfWorkers = instanceNames.size();
	    	int newWorkers = currentNumberOfWorkers*expandRatio-currentNumberOfWorkers;
            
	        for (int i= 0; i < newWorkers; i++) {
	            
	            // Start a new Instance
	    		RunInstancesResult result = ec2.runInstances(getRunSettings());
	            
	            // Obtain new instance data
	    		Reservation reservation = result.getReservation();
	    		List<Instance> instances = reservation.getInstances();
	    		Instance inst = instances.get(0);
	    	
	            // Add new Instance to load balancer
	            // Note the conflict between Instance classes
	    		com.amazonaws.services.elasticloadbalancing.model.Instance lbInstance = new com.amazonaws.services.elasticloadbalancing.model.Instance(inst.getInstanceId());
	    		List <com.amazonaws.services.elasticloadbalancing.model.Instance> newInstances = Arrays.asList(lbInstance);
	    		
	    		RegisterInstancesWithLoadBalancerRequest lbRequest = new RegisterInstancesWithLoadBalancerRequest(lbName, newInstances);
	    		RegisterInstancesWithLoadBalancerResult loadBalancerResult = elasticLoadBalancer.registerInstancesWithLoadBalancer(lbRequest);
	    
	        }
	    } catch (AmazonClientException ace) {
	        handleAmazonClientException(ace, out);
	    }
    }
    
    
    public static void stopInstances(BasicAWSCredentials awsCredentials, int shrinkRatio, PrintWriter out) {
		
        //Find number of workers to increment
        List<String> instanceNames = getInstanceNames(awsCredentials, out);
		int currentNumberOfWorkers = instanceNames.size();
        
        // Avoid division by 0
        shrinkRatio = (shrinkRatio == 0) ? shrinkRatio : 1;

		// In this case, numberOfWorkersToStop = 0 if shrinkRatio == 1
		int numberOfWorkersToStop = currentNumberOfWorkers-(int)(currentNumberOfWorkers/shrinkRatio);

    	AmazonElasticLoadBalancing elasticLoadBalancer = new AmazonElasticLoadBalancingClient(awsCredentials);
        AmazonEC2 ec2 = new AmazonEC2Client(awsCredentials);
        String instanceId = new String();
        try {
	
            for (int i = 0; i<numberOfWorkersToStop; i++ ){
                instanceId = instanceNames.get(i);
                
                // Deregister from loadbalancer
                com.amazonaws.services.elasticloadbalancing.model.Instance lbInstance = new com.amazonaws.services.elasticloadbalancing.model.Instance(instanceId);
                List <com.amazonaws.services.elasticloadbalancing.model.Instance> lbInstances = Arrays.asList(lbInstance);
                DeregisterInstancesFromLoadBalancerRequest deregisterRequest = new DeregisterInstancesFromLoadBalancerRequest(lbName,lbInstances);
                DeregisterInstancesFromLoadBalancerResult deregisterResult = elasticLoadBalancer.deregisterInstancesFromLoadBalancer(deregisterRequest);

                // declare a list of instanceIds since terminateinstancerequest only takes list
                List <String> instanceIds = Arrays.asList(instanceId);
                TerminateInstancesResult result = ec2.terminateInstances(new TerminateInstancesRequest(instanceIds));
            }
         
        } catch (AmazonClientException ace) {
            handleAmazonClientException(ace, out);
        }
    }
    
    public static List<String> getInstanceNames(BasicAWSCredentials awsCredentials, PrintWriter out) {
    	
    	List <String> instanceNames = new ArrayList<String>();
    	try {
    		
            AmazonCloudWatch cw = new AmazonCloudWatchClient(awsCredentials);
        	ListMetricsRequest listMetricsRequest = new ListMetricsRequest();
        	listMetricsRequest.setMetricName("CPUUtilization");
        	listMetricsRequest.setNamespace("AWS/EC2");
        	ListMetricsResult result = cw.listMetrics(listMetricsRequest);
        	java.util.List<Metric> 	metrics = result.getMetrics();
        	// initialize a list
        	
        	for (Metric metric : metrics) {
        		String namespace = metric.getNamespace();
        		String metricName = metric.getMetricName();
        		List<Dimension> dimensions = metric.getDimensions();
            	GetMetricStatisticsRequest statisticsRequest = new GetMetricStatisticsRequest();
            	statisticsRequest.setNamespace(namespace);
            	statisticsRequest.setMetricName(metricName);
            	statisticsRequest.setDimensions(dimensions);
            	Date endTime = new Date();
            	Date startTime = new Date();
				// set the period between start time to end time to be 3 mins. Sometimes the datapoint is not shown if the period is set too small
            	startTime.setTime(endTime.getTime()- 180000);
            	statisticsRequest.setStartTime(startTime);
            	statisticsRequest.setEndTime(endTime);
            	statisticsRequest.setPeriod(60);
            	Vector<String>statistics = new Vector<String>();
            	statistics.add("Maximum");
            	statisticsRequest.setStatistics(statistics);
            	GetMetricStatisticsResult stats = cw.getMetricStatistics(statisticsRequest);
				
				if (dimensions.isEmpty()==false) {
					// we also do not want to print out images with imageid and instance type
					if (dimensions.get(0).getName().equals("InstanceId")) {
						// make sure we do not give the user to option to shutdown the ManagerUI instance itself
						////////////////////////////////
						///// if you do not want to show a particular instanceID////
						///// modify the following line////////////////////////////
						//////////////////////////////////////////////////////////
						if (dimensions.get(0).getValue().equals("i-07dd7863")==false) {
						// also make sure that the instance is running by checking the stats				
							if (stats.getDatapoints().size()>=1) {
							    //add the instanceID to the current instanceNames
								instanceNames.add(dimensions.get(0).getValue());
								
							}
						}
					}
				}
            }
        } catch (AmazonServiceException ase) {
            handleAmazonServiceException(ase, out);
        } catch (AmazonClientException ace) {
            handleAmazonClientException(ace, out);
        }
        return instanceNames;
    }
    
    private static void handleAmazonServiceException(AmazonServiceException ase, PrintWriter out){
        out.println("Caught an AmazonServiceException, which means your request made it "
                        + "to Amazon EC2, but was rejected with an error response for some reason.");
                out.println("Error Message:    " + ase.getMessage());
                out.println("HTTP Status Code: " + ase.getStatusCode());
                out.println("AWS Error Code:   " + ase.getErrorCode());
                out.println("Error Type:       " + ase.getErrorType());
                out.println("Request ID:       " + ase.getRequestId());
    }
    private static void handleAmazonClientException(AmazonClientException ace, PrintWriter out){
        out.println("Caught an AmazonClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with EC2, "
                    + "such as not being able to access the network.");
        out.println("Error Message: " + ace.getMessage());
    }
}
